package com.google.code.p.restrpc.client.cluster;

import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.code.p.restrpc.RpcClientException;
import com.google.code.p.restrpc.RpcException;
import com.google.code.p.restrpc.RpcRequest;
import com.google.code.p.restrpc.RpcResponse;
import com.google.code.p.restrpc.client.event.AsyncRpcEventUtils;
import com.google.code.p.restrpc.client.loadbalance.LoadBalanceFailedException;
import com.google.code.p.restrpc.client.proxy.RemoteNodeRefusedException;

/**
 * @author dz
 */
public class FailOverStrategy extends AbstractFailureRecoveryStrategy {
    
    private static final Log logger = LogFactory.getLog(FailOverStrategy.class);
    
    @Override
    public RpcResponse invoke(ClusteredRpcContext context,
                              List<RpcRequest> requests) throws RpcException {
        RpcResponse result = null;
        int nubmerOfRetries = context.getRpcContext().getNumberOfRetries() < 0 ? 1
                                                                              : context.getRpcContext()
                                                                                       .getNumberOfRetries();
        RpcRequest[] requestArray = (RpcRequest[]) requests.toArray(new RpcRequest[] {});
        int selectedPath = context.getLoadBalanceStrategy()
                                  .select(context.getRpcContext()
                                                 .getServiceName(),
                                          context.getRpcContext()
                                                 .getMethodName(),
                                          requestArray);
        if (selectedPath < 0) {
            throw new LoadBalanceFailedException("Can't determine the destination of rpcRequest!");
        }
        
        try {
            RpcRequest rpcRequest = requests.get(selectedPath);
            result = context.getRestInvoker().send(rpcRequest);
            
            if (result.isSucceed()) {
                if (rpcRequest.isAsync()) {
                    AsyncRpcEventUtils.publishAsyncRpcEvent(context.getRpcContext(),
                                                            rpcRequest);
                }
                
                return result;
            }
            else if (result.getFault() != null && result.getFault()
                                                        .isBizError()) {
                return result;
            }
            
            if (nubmerOfRetries <= 0) {
                return result;
            }
        }
        catch (RemoteNodeRefusedException e) {
            logger.error(e, e);
            disableRemoteServiceDefinition(context, requests.get(selectedPath));
            if (nubmerOfRetries <= 0) {
                throw e;
            }
        }
        catch (RpcClientException e) {
            logger.error(e, e);
            if (nubmerOfRetries <= 0) {
                throw e;
            }
        }
        
        for (int i = 1; i <= nubmerOfRetries; i++) {
            logger.debug("retry " + i);
            try {
                selectedPath = context.getLoadBalanceStrategy()
                                      .select(context.getRpcContext()
                                                     .getServiceName(),
                                              context.getRpcContext()
                                                     .getMethodName(),
                                              requestArray);
                if (selectedPath < 0) {
                    throw new LoadBalanceFailedException("Can't determine the destination of rpcRequest!");
                }
                
                RpcRequest rpcRequest = requests.get(selectedPath);
                result = context.getRestInvoker().send(rpcRequest);
                
                if (result.isSucceed()) {
                    if (rpcRequest.isAsync()) {
                        AsyncRpcEventUtils.publishAsyncRpcEvent(context.getRpcContext(),
                                                                rpcRequest);
                    }
                    
                    return result;
                }
                else if (result.getFault() != null && result.getFault()
                                                            .isBizError()) {
                    return result;
                }
            }
            catch (RemoteNodeRefusedException e) {
                logger.error(e, e);
                disableRemoteServiceDefinition(context,
                                               requests.get(selectedPath));
                if (i == nubmerOfRetries) {
                    throw e;
                }
            }
            catch (RpcClientException e) {
                logger.error(e, e);
                if (i == nubmerOfRetries) {
                    throw e;
                }
            }
        }
        
        return result;
    }
}
